package com.sc.nft.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.mate.sdk.domain.Key;
import com.mate.sdk.domain.OwnerResult;
import com.sc.nft.dao.*;
import com.sc.nft.entity.*;
import com.sc.nft.enums.*;
import com.sc.nft.exception.GlobalRunTimeException;
import com.sc.nft.helper.MetaChainHepler;
import com.sc.nft.service.ChainCallbackService;
import com.sc.nft.service.CollectionChainInitDetailsLogService;
import com.sc.nft.service.UserChainInfoService;
import com.sc.nft.util.WeChatUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户链信息表服务接口实现
 *
 * @author kancy
 * @description 由 Mybatisplus Code Generator 创建
 * @since 2022-10-20 23:17:03
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class UserChainInfoServiceImpl implements UserChainInfoService {
    private final UserChainInfoDao userChainInfoDao;
    private final DigitalCollectionDao digitalCollectionDao;
    private final UserCollectionDao userCollectionDao;
    private final UserCollectionChainLogDao userCollectionChainLogDao;
    private final CollectionChainInitDetailsLogService collectionChainInitDetailsLogService;
    private final DaoChainInfoDao daoChainInfoDao;
    private final DaoCollectionStoreDao daoCollectionStoreDao;
    private final MetaChainHepler metaChainHepler;
    private final ChainCallBackLogDao chainCallBackLogDao;
    private final ChainCallbackService chainCallbackService;
    private final TransferCollectionWithoutRecordsDao transferCollectionWithoutRecordsDao;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void userRegister(UserChainInfo userChainInfo) {
        Key key = metaChainHepler.userRegister();
        log.info("用户id:{},地址:{},私钥:{},公钥:{}", userChainInfo.getUserId(), key.getAddress(), key.getPrivateKey(), key.getPublicKey());
        Assert.isTrue(userChainInfoDao.updatePublicKeyAndAddressAndStatusById(key.getPublicKey(), key.getAddress(), key.getPrivateKey(), Boolean.TRUE, userChainInfo.getId()), () -> new GlobalRunTimeException("用户链信息更新失败!"));

        ChainRequestLog userRegisterLog = new ChainRequestLog();
        userRegisterLog.setMethodName(ChainCallBackTypeEnum.USER_REGISTER.getDescription());
        userRegisterLog.setRequestUrl(StrUtil.EMPTY);
        userRegisterLog.setRequestParams(JSONUtil.toJsonStr(userChainInfo));
        userRegisterLog.setMethodType(ChainCallBackTypeEnum.USER_REGISTER);
        userRegisterLog.insert();

        //给地址转账1个代币
        metaChainHepler.transferBalance(userChainInfoDao.getByUserId(1L).getUserKey(), key.getAddress(), Convert.toBigInteger(100000000000L));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void mint(CollectionChainInitDetailsLog collectionChainInitDetailsLog) {
        UserChainInfo userChainInfo = userChainInfoDao.getById(1);
        log.info("铸造地址:{}", userChainInfo.getAddress());
        if (ObjectUtil.isNull(userChainInfo)) {
            log.warn("用户{}未注册上链", userChainInfo.getUserId());
            return;
        }

        Assert.isTrue(collectionChainInitDetailsLog.getStatus() == UserChainLogStatusEnum.WAIT, () -> new GlobalRunTimeException("请勿重复上链!"));
        Assert.isTrue(collectionChainInitDetailsLog.getDealStatus() == CollectionChainDealStatusEnum.IN_CHINA, () -> new GlobalRunTimeException("请勿重复上链!"));

        DigitalCollection digitalCollection = digitalCollectionDao.getById(collectionChainInitDetailsLog.getCollectionId());
        if (ObjectUtil.isNull(digitalCollection)) {
            log.warn("藏品{}未找到", digitalCollection.getId());
            return;
        }
        //执行链上铸造
        String transactionHash = metaChainHepler.mint(userChainInfo.getUserKey(), digitalCollection.getContractAddress(),
                collectionChainInitDetailsLog.getCollectionUuid(), digitalCollection.getFirstTitle(),
                digitalCollection.getIssuePrice().setScale(2, BigDecimal.ROUND_DOWN).toPlainString(), digitalCollection.getCreationContent());
        //批量更新交易Hash
        Assert.isTrue(collectionChainInitDetailsLogService.updateContractAddressAndTransactionHashAndStatusAndDealStatusById(digitalCollection.getContractAddress(),
                        transactionHash, UserChainLogStatusEnum.IN_CHINA, CollectionChainDealStatusEnum.IN_CHINA, collectionChainInitDetailsLog.getId()),
                () -> new GlobalRunTimeException("更新仓库数据失败!"));

    }

    @Override
    public void mintBatch(List<CollectionChainInitDetailsLog> list) {

        UserChainInfo userChainInfo = userChainInfoDao.getById(1);
        log.info("铸造地址:{}", userChainInfo.getAddress());
        if (ObjectUtil.isNull(userChainInfo)) {
            log.warn("用户{}未注册上链", userChainInfo.getUserId());
            return;
        }

        DigitalCollection digitalCollection = digitalCollectionDao.getById(list.get(0).getCollectionId());
        if (ObjectUtil.isNull(digitalCollection)) {
            log.warn("藏品{}未找到", digitalCollection.getId());
            return;
        }

        List<String> uuids = list.stream().map(CollectionChainInitDetailsLog::getCollectionUuid).collect(Collectors.toList());
        DateTime now = DateTime.now();
        log.info(StrUtil.format("批量铸造开始时间:{}", now));
        //执行链上批量铸造
        String transactionHash = metaChainHepler.mintBatch(userChainInfo.getUserKey(), digitalCollection.getContractAddress(), uuids, digitalCollection.getFirstTitle(), digitalCollection.getIssuePrice().setScale(2, BigDecimal.ROUND_DOWN).toPlainString(), digitalCollection.getCreationContent());
        DateTime end = DateTime.now();
        log.info("--------------------------------------------------------------------------------------");
        log.info(StrUtil.format("批量铸造开始时间:{}", end));
        log.info(StrUtil.format("批量铸造整体执行时间:{}", end.getTime() - now.getTime()));
        log.info("--------------------------------------------------------------------------------------");
        //批量更新交易Hash
        Assert.isTrue(collectionChainInitDetailsLogService.updateContractAddressAndTransactionHashAndStatusAndDealStatusByIds(digitalCollection.getContractAddress(), transactionHash, UserChainLogStatusEnum.IN_CHINA, CollectionChainDealStatusEnum.IN_CHINA, list.stream().map(CollectionChainInitDetailsLog::getId).collect(Collectors.toList())), () -> new GlobalRunTimeException("更新仓库数据失败!"));

    }

    @Override
    public void chainCallback(ChainCallBackLog chainCallBackLog) throws IllegalAccessException {

        switch (chainCallBackLog.getCallBackType()) {
            case DEPLOY://铸造
                chainCallbackService.deploy(chainCallBackLog);
                break;
            case MINT://发行
                chainCallbackService.mint(chainCallBackLog);
                break;
            case MINT_BATCH://批量发行
                chainCallbackService.mintBatch(chainCallBackLog);
                break;
            case TRANSFER://转移
                chainCallbackService.transfer(chainCallBackLog);
                break;
            case BURN://销毁
                chainCallbackService.burn(chainCallBackLog);
                break;
            case UPDATE_META_DATA://修改元数据
                chainCallbackService.updateMetaData(chainCallBackLog);
                break;
            case UPDATE_META_DATA_BATCH://批量修改元数据
                chainCallbackService.updateMetaDataBatch(chainCallBackLog);
                break;
            case TRANSFER_BALANCE://代币转账
                chainCallbackService.transferBalance(chainCallBackLog);
                break;
            case RECYCLE://系统回收
                chainCallbackService.recycleCollection(chainCallBackLog);
                break;
            case ECOLOGY_DESTROY://生态销毁
                chainCallbackService.ecologyDestroyCollection(chainCallBackLog);
                break;
            case TRADING_DESTROY://交易池藏品销毁
                chainCallbackService.tradingDestroyCollection(chainCallBackLog);
                break;
            case TRANSFER_TO_META_WALLET://转增到玛特钱包
                chainCallbackService.transferToMetaWallet(chainCallBackLog);
                break;
            case TRANSFER_FROM_META_WALLET://玛特钱包转入
                chainCallbackService.transferFromMetaWallet(chainCallBackLog);
                break;
            case PLEDGE_DESTROY://质押藏品回收
                chainCallbackService.pledgeDestroyCollection(chainCallBackLog);
                break;
        }


    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transfer(UserCollectionChainLog userCollectionChainLog) {
        //来源用户
        UserChainInfo fromUserInfo = userChainInfoDao.getRegisterByUserId(userCollectionChainLog.getFromUserId());
        //目标用户
        UserChainInfo chainUserInfo = userChainInfoDao.getRegisterByUserId(userCollectionChainLog.getUserId());

        //DAO仓库
        DaoChainInfo daoChainInfo = daoChainInfoDao.getByDaoId(userCollectionChainLog.getDaoId());

        //如果目标用户和DAO仓库 都未注册上链，则不处理
        if (ObjectUtil.hasNull(chainUserInfo, fromUserInfo) && ObjectUtil.isNull(daoChainInfo)) {
            log.warn("用户{}.{}未注册上链", chainUserInfo.getId(), fromUserInfo.getId());
            return;
        }

        //判断目标用户是dao还是普通用户
        String toAddress = ObjectUtil.isNull(chainUserInfo) ? daoChainInfo.getAddress() : chainUserInfo.getAddress();
        String contractAddress = userCollectionChainLog.getContractAddress();
        String deployTransactionHash = userCollectionChainLog.getDeployTransactionHash();
        Integer tokenId = userCollectionChainLog.getTokenId();
        String address = userCollectionChainLog.getFromUserAddress();
        //校验来源用户
        if (userCollectionChainLog.getFromUserCollectionId() == null || userCollectionChainLog.getFromUserCollectionId().longValue() == 0L) {
            address = userChainInfoDao.getRegisterByUserId(1L).getAddress();
        } else {
            UserCollectionChainLog originalChainLog = userCollectionChainLogDao.getByCollectionId(userCollectionChainLog.getFromUserCollectionId());
            if (ObjectUtil.isNotNull(originalChainLog)) {
                address = originalChainLog.getAddress();
            }
        }

        //执行链上交易
        String transactionHash = metaChainHepler.transfer(fromUserInfo.getUserKey(), contractAddress, tokenId, toAddress);

        //修改状态
        userCollectionChainLogDao.updateContractAddressAndDeployTransactionHashAndTransactionHashAndAddressAndTokenIdAndStatusById(
                userCollectionChainLog.getId(), contractAddress, deployTransactionHash, transactionHash, address, tokenId, UserChainLogStatusEnum.IN_CHINA);

    }

    @Override
    public void transferToMetaWallet(UserCollectionChainLog userCollectionChainLog) {
        //来源用户
        UserChainInfo fromUserInfo = userChainInfoDao.getRegisterByUserId(userCollectionChainLog.getFromUserId());

        UserChainInfo toUserAddr = userChainInfoDao.getRegisterByUserId(1L);
        //执行链上交易
        String transactionHash = metaChainHepler.transferToMetaWallet(fromUserInfo.getUserKey(), userCollectionChainLog.getContractAddress(), userCollectionChainLog.getTokenId(), toUserAddr.getAddress());

        //修改状态
        userCollectionChainLogDao.updateContractAddressAndDeployTransactionHashAndTransactionHashAndAddressAndTokenIdAndStatusById(
                userCollectionChainLog.getId(), userCollectionChainLog.getContractAddress(), userCollectionChainLog.getDeployTransactionHash(), transactionHash, userCollectionChainLog.getFromUserAddress(), userCollectionChainLog.getTokenId(), UserChainLogStatusEnum.IN_CHINA);

        transferCollectionWithoutRecordsDao.updateStatusByUserCollectionChainId(userCollectionChainLog.getId(), TransferCollectionWithoutStatusEnum.EXECUTING, transactionHash, StrUtil.EMPTY);

    }

    @Override
    public void transferFromMetaWallet(UserCollectionChainLog userCollectionChainLog) {
        //来源用户
        UserChainInfo fromUserInfo = userChainInfoDao.getRegisterByUserId(1L);
        //目标用户
        UserChainInfo chainUserInfo = userChainInfoDao.getRegisterByUserId(userCollectionChainLog.getUserId());

        //如果目标用户和DAO仓库 都未注册上链，则不处理
        if (ObjectUtil.hasNull(chainUserInfo, fromUserInfo)) {
            log.warn("用户{}.{}未注册上链", chainUserInfo.getId(), fromUserInfo.getId());
            return;
        }

        //判断目标用户是dao还是普通用户
        String toAddress = chainUserInfo.getAddress();
        String contractAddress = userCollectionChainLog.getContractAddress();
        String deployTransactionHash = userCollectionChainLog.getDeployTransactionHash();
        Integer tokenId = userCollectionChainLog.getTokenId();
        String address = fromUserInfo.getAddress();

        //执行链上交易
        String transactionHash = metaChainHepler.transferFromMetaWallet(fromUserInfo.getUserKey(), contractAddress, tokenId, toAddress);

        //修改状态
        userCollectionChainLogDao.updateContractAddressAndDeployTransactionHashAndTransactionHashAndAddressAndTokenIdAndStatusById(
                userCollectionChainLog.getId(), contractAddress, deployTransactionHash, transactionHash, address, tokenId, UserChainLogStatusEnum.IN_CHINA);

        transferCollectionWithoutRecordsDao.updateStatusByUserCollectionChainId(userCollectionChainLog.getId(), TransferCollectionWithoutStatusEnum.EXECUTING, StrUtil.EMPTY, StrUtil.EMPTY);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void faultTolerantChainCallback(ChainCallBackLog chainCallBackLog) {
        UserCollectionChainLog userCollectionChainLog = userCollectionChainLogDao.getOneByTransactionHashAndTokenId(chainCallBackLog.getTransactionHash(), chainCallBackLog.getTokenId());
        //交易逻辑
        if (userCollectionChainLog.getType() == UserChainTypeEnum.TRANSFER || userCollectionChainLog.getType() == UserChainTypeEnum.RECYCLE) {
            //获取当前藏品的持有者
            String nftOwner = metaChainHepler.getNFTOwner(userCollectionChainLog.getContractAddress(), userCollectionChainLog.getTokenId());
            //如果持有者是 则修改回调日志为成功,并且是待执行状态
            if (userCollectionChainLog.getToUserAddress().equals(nftOwner)) {
                log.info("用户链日志表id:{}链上交易成功,修改sc_chain_call_back_log 为回调待执行状态", userCollectionChainLog.getId());
                Assert.isTrue(chainCallBackLogDao.updateStatusAndIsSuccessById(chainCallBackLog.getId(), ChainCallBackStatusEnum.TO_BE_PROCESSED, Boolean.TRUE), () -> new GlobalRunTimeException("修改失败"));
            } else if (userCollectionChainLog.getFromUserAddress().equals(nftOwner)) {
                //如果持有者是来源地址,则修改链日志为待执行状态,重新执行
                Assert.isTrue(userCollectionChainLogDao.updateStatusById(userCollectionChainLog.getId(), UserChainLogStatusEnum.WAIT), () -> new GlobalRunTimeException("修改失败"));
                log.info("用户链日志表{}交易失败,修改sc_user_collection_chain_log 为待执行状态", userCollectionChainLog.getId());
            }

            //如果是销毁逻辑
        } else if (userCollectionChainLog.getType() == UserChainTypeEnum.BURN || userCollectionChainLog.getType() == UserChainTypeEnum.ECOLOGY_DESTROY) {
            OwnerResult nftOwner = metaChainHepler.getNFTOwnerByResult(userCollectionChainLog.getContractAddress(), userCollectionChainLog.getTokenId());
            if (StrUtil.equals(nftOwner.getCode(), "2041")) {//直接结果是成功的
                if ("NFT not exists.".equals(nftOwner.getMessage())) {
                    //修改链日志为成功并且是待执行
                    Assert.isTrue(chainCallBackLogDao.updateStatusAndIsSuccessById(chainCallBackLog.getId(), ChainCallBackStatusEnum.PROCESSED_SUCESS, Boolean.TRUE), () -> new GlobalRunTimeException("修改失败"));
                    Assert.isTrue(userCollectionChainLogDao.updateStatusById(userCollectionChainLog.getId(), UserChainLogStatusEnum.SUCCESS), () -> new GlobalRunTimeException("修改失败"));
                    log.info("用户链日志表{}销毁成功,修改sc_chain_call_back_log 为回调待执行状态", userCollectionChainLog.getId());
                } else {
                    WeChatUtil.sendWeChatMessage(StrUtil.format("用户链日志表{}销毁失败,原因:{}", userCollectionChainLog.getId(), nftOwner.getMessage()));
                }
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void recycleCollection(UserCollectionChainLog userCollectionChainLog) {
        //来源用户
        UserChainInfo fromUserInfo = userChainInfoDao.getRegisterByUserId(userCollectionChainLog.getFromUserId());
        //目标用户
        UserChainInfo toUser = userChainInfoDao.getRegisterByUserId(1L);

        //如果目标用户和DAO仓库 都未注册上链，则不处理
        if (ObjectUtil.hasNull(toUser, fromUserInfo)) {
            log.warn("用户{}.{}未注册上链", toUser.getId(), fromUserInfo.getId());
            return;
        }

        String transactionHash = metaChainHepler.recycleCollection(fromUserInfo.getUserKey(), userCollectionChainLog.getContractAddress(), userCollectionChainLog.getTokenId(), toUser.getAddress());
        //修改状态
        userCollectionChainLogDao.updateContractAddressAndDeployTransactionHashAndTransactionHashAndAddressAndTokenIdAndStatusById(
                userCollectionChainLog.getId(), userCollectionChainLog.getContractAddress(), userCollectionChainLog.getDeployTransactionHash(), transactionHash, userCollectionChainLog.getAddress(), userCollectionChainLog.getTokenId(), UserChainLogStatusEnum.IN_CHINA);
    }

    @Override
    public void ecologyDestroyCollection(UserCollectionChainLog userCollectionChainLog) {
        //来源用户
        UserChainInfo userChainInfo = userChainInfoDao.getRegisterByUserId(userCollectionChainLog.getUserId());
        if (ObjectUtil.isNull(userChainInfo)) {
            log.warn("用户{}.{}未注册上链", userChainInfo.getUserId());
            return;
        }
        UserCollection userCollection = userCollectionDao.getById(userCollectionChainLog.getUserCollectionId());
        if (ObjectUtil.isNull(userCollection)) {
            return;
        }

        String transactionHash = metaChainHepler.ecologyDestroyCollection(userChainInfo.getUserKey(), userCollectionChainLog.getContractAddress(), userCollectionChainLog.getTokenId());
        //修改状态
        userCollectionChainLog.setCollectionType(ReceivedCollectionType.ECOLOGY_DESTROY);
        userCollectionChainLog.setStatus(UserChainLogStatusEnum.IN_CHINA);
        userCollectionChainLog.setTransactionHash(transactionHash);
        userCollectionChainLog.updateById();
        Assert.isTrue(userCollectionChainLogDao.updateStatusAndTransactionHashById(userCollectionChainLog.getId(), UserChainLogStatusEnum.IN_CHINA, transactionHash), () -> new GlobalRunTimeException("更新链上交易hash失败!"));
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void pledgeDestroyCollection(UserCollectionChainLog userCollectionChainLog) {
        //来源用户
        UserChainInfo userChainInfo = userChainInfoDao.getRegisterByUserId(userCollectionChainLog.getUserId());


        String transactionHash = metaChainHepler.pledgeDestroyCollection(userChainInfo.getUserKey(), userCollectionChainLog.getContractAddress(), userCollectionChainLog.getTokenId());

        Assert.isTrue(userCollectionChainLogDao.updateStatusAndTransactionHashById(userCollectionChainLog.getId(), UserChainLogStatusEnum.IN_CHINA, transactionHash), () -> new GlobalRunTimeException("更新链上交易hash失败!"));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void tradingDestroyCollection(UserCollectionChainLog userCollectionChainLog) {
        //来源用户
        UserChainInfo userChainInfo = userChainInfoDao.getRegisterByUserId(userCollectionChainLog.getUserId());


        String transactionHash = metaChainHepler.tradingDestroyCollection(userChainInfo.getUserKey(), userCollectionChainLog.getContractAddress(), userCollectionChainLog.getTokenId());

        Assert.isTrue(userCollectionChainLogDao.updateStatusAndTransactionHashById(userCollectionChainLog.getId(), UserChainLogStatusEnum.IN_CHINA, transactionHash), () -> new GlobalRunTimeException("更新链上交易hash失败!"));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void burnNFT(UserCollection userCollection) {
        UserChainInfo userChainInfo = userChainInfoDao.getRegisterByUserId(userCollection.getUserId());
        if (ObjectUtil.isNull(userChainInfo)) {
            log.warn("用户{}未注册上链", userCollection.getId());
            return;
        }

        UserCollectionChainLog originalChainLog = userCollectionChainLogDao.getByCollectionId(userCollection.getId());
        if (ObjectUtil.isNull(originalChainLog)) {
            return;
        }

        String transactionHash = metaChainHepler.burnNFT(userChainInfo.getUserKey(), originalChainLog.getContractAddress(), originalChainLog.getTokenId());
        UserCollectionChainLog userCollectionChainLog = new UserCollectionChainLog();
        userCollectionChainLog.setUserId(userCollection.getUserId());
        userCollectionChainLog.setUserCollectionId(userCollection.getId());
        userCollectionChainLog.setType(UserChainTypeEnum.BURN);
        userCollectionChainLog.setStatus(UserChainLogStatusEnum.IN_CHINA);
        userCollectionChainLog.setCollectionId(userCollection.getCollectionId());
        userCollectionChainLog.setFromUserAddress(userChainInfo.getAddress());
        userCollectionChainLog.setFromUserId(userChainInfo.getUserId());
        userCollectionChainLog.setFromUserCollectionId(userCollection.getId());
        userCollectionChainLog.setContractAddress(originalChainLog.getContractAddress());
        userCollectionChainLog.setDeployTransactionHash(originalChainLog.getDeployTransactionHash());
        userCollectionChainLog.setTransactionHash(transactionHash);
        userCollectionChainLog.setProductId(originalChainLog.getProductId());
        userCollectionChainLog.setAddress(originalChainLog.getAddress());
        userCollectionChainLog.setTokenId(originalChainLog.getTokenId());
        userCollectionChainLog.setCollectionType(ReceivedCollectionType.SYNTHESIS);
        userCollectionChainLog.insert();
        userCollectionDao.updateChainUrl(userCollection.getId(), userCollectionChainLog.getContractAddress(), ChainStatusEnum.OFFLINE);
    }

    @Override
    public UserChainInfo getByUserId(Long userId) {
        return userChainInfoDao.getRegisterByUserId(userId);
    }


}